package com.easyaop.api.support.loadclass;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Java17ClassDefiner extends AbstractClassDefiner {
    private static final Method DEFINE_HIDDEN_CLASS_METHOD;
    private static final Class<?> CLASS_OPTION_CLASS;

    private static final Object NESTMATE;

    private static final Object STRONG;

    public Java17ClassDefiner(int majorVersion, MethodHandles.Lookup caller, Class<?> targetClass) {
        super(majorVersion, caller, targetClass);
    }

    @Override
    public Class<?> defineClass(byte[] classBytes) throws Exception {
        Object[] array = (Object[]) Array.newInstance(CLASS_OPTION_CLASS, 2);
        array[0] = NESTMATE;
        array[1] = STRONG;
        return ((MethodHandles.Lookup) DEFINE_HIDDEN_CLASS_METHOD.invoke(caller, classBytes, true,
                array)).lookupClass();
    }

    @Override
    public MethodHandle find(Class<?> refc, String name, MethodType type) {
        try {
            return caller.findConstructor(refc, type);
        } catch (NoSuchMethodException | IllegalAccessException ex) {
            throw new RuntimeException(ex);
        }
    }

    private static Method getDefineHiddenClassMethod() {
        try {
            Class<?> lookupClass = Class.forName("java.lang.invoke.MethodHandles$Lookup");
            Class<?> array = Array.newInstance(CLASS_OPTION_CLASS, 0).getClass();
            return lookupClass.getDeclaredMethod("defineHiddenClass", byte[].class, Boolean.TYPE,
                    array);
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    private static Object getClassOption(Class<?> classOption, String name) {
        try {
            Field field = classOption.getDeclaredField(name);
            field.setAccessible(true);
            return field.get(null);
        } catch (NoSuchFieldException | IllegalAccessException ex) {
            throw new RuntimeException(ex);
        }
    }

    static {
        Class<?> classOption = null;
        try {
            classOption = Class.forName("java.lang.invoke.MethodHandles$Lookup$ClassOption");
        } catch (ClassNotFoundException ex) {
            throw new RuntimeException(ex);
        }
        CLASS_OPTION_CLASS = classOption;
        DEFINE_HIDDEN_CLASS_METHOD = getDefineHiddenClassMethod();
        NESTMATE = getClassOption(classOption, "NESTMATE");
        STRONG = getClassOption(classOption, "STRONG");
    }
}